feat: 사이드 프로젝트 스프린트 보드·진행률 차트 DB 연동 - #44
Conversation
…ect-sprint-backend
📝 WalkthroughWalkthrough사이드 프로젝트의 스프린트·태스크 기능을 Mock 기반에서 Supabase와 React Query 기반으로 전환했습니다. 스프린트 보드 CRUD·DnD·백로그 이동, 진행률 차트, 라우팅과 관련 UI·타입·매퍼를 추가하거나 갱신했습니다. Changes사이드 프로젝트 기능 연동
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/features/manage-calendar/ui/CalendarView.tsx (1)
329-343: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win라벨-인풋 연결 누락 (접근성)
일정 이름라벨이htmlFor/id또는 중첩 없이 입력과 분리되어 있어, 스크린리더 사용자가 어떤 입력과 연결된 라벨인지 알 수 없습니다. 정적 분석 도구에서도 동일하게 플래그되었습니다.♿ 제안 수정
- <label className="text-brand-muted block text-[12px] font-semibold"> + <label htmlFor="calendar-event-title" className="text-brand-muted block text-[12px] font-semibold"> 일정 이름 <span className="text-[`#ff6565`]">*</span> </label> <input + id="calendar-event-title" type="text"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/manage-calendar/ui/CalendarView.tsx` around lines 329 - 343, Update the 일정 이름 label and its associated input in the CalendarView form to use a matching htmlFor and id, ensuring the label explicitly references this title field without changing the existing validation or input behavior.Source: Linters/SAST tools
src/views/side-project/sprint-board/ui/SprintBoardView.tsx (1)
67-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win스프린트 전환 시 보드만 로딩 처리하세요
tasksQuery.isPending || backlogQuery.isPending에서 바로return해서, 새 스프린트를 처음 열 때SprintSelector/SprintToolbar/SprintSummaryHeader까지 같이 사라집니다.SprintBoard만 조건부로 감싸고,useSprintTasks에는placeholderData를 넣어 전환 깜빡임을 줄이는 편이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/views/side-project/sprint-board/ui/SprintBoardView.tsx` around lines 67 - 89, Update the loading flow in SprintBoardView so tasksQuery.isPending or backlogQuery.isPending no longer returns before rendering the sprint selector, toolbar, and summary header. Keep the surrounding sprint UI visible, conditionally render only SprintBoard for the loading state, and configure useSprintTasks with placeholderData to preserve previous task data during sprint transitions and reduce flicker.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/entities/side-project/sprint/api/update-sprint.ts`:
- Around line 13-26: Update updateSprint to require and apply a workspace_id
equality filter alongside the existing id filter when updating sprints, using
the authenticated workspace context rather than trusting an arbitrary client
value. Also remove the permissive dev_full_access RLS policy from the production
database configuration or migration.
In `@src/entities/side-project/task/api/create-task.ts`:
- Line 3: Update the task creation flow in create-task.ts to use the shared
getCurrentUserId() helper from current-user.ts instead of DEV_USER_ID when
populating created_by, preserving the existing validation and insert behavior so
development-only fallback remains centralized in the helper.
In `@src/entities/side-project/task/api/get-backlog-tasks.ts`:
- Around line 7-20: Update the database RLS configuration governing the tasks
query used by getBacklogTasks to remove or narrowly restrict the public.*
dev_full_access policy, ensuring access is limited to the authenticated user’s
workspace membership while preserving legitimate backlog reads.
In `@src/entities/side-project/task/api/use-update-task-status.ts`:
- Around line 30-34: Update the rollback loop in the mutation’s onError handler
to safely skip iteration when context or its previous snapshot is undefined,
while preserving restoration of every snapshot when available. Keep the existing
error toast behavior unchanged.
In `@src/entities/side-project/task/model/task.schema.ts`:
- Line 16: Update the assigneeId schema to use Zod’s standard z.uuid() validator
followed by .nullable(), replacing the current z.string().uuid() chain while
preserving nullable UUID validation.
In `@src/features/manage-calendar/ui/CalendarView.tsx`:
- Around line 353-365: Connect the “시간 (선택)” label to its input by adding a
matching htmlFor and id pair around the time field in CalendarView, using a
unique identifier and preserving the existing input behavior.
In `@src/features/manage-sprint-tasks/model/use-task-dnd.ts`:
- Around line 9-41: Extend useTaskDnd beyond native drag events by exposing a
keyboard- and single-pointer-accessible status-change action, such as a status
selector or change handler that invokes onMove with the task ID and target
TaskStatus. Integrate this alternative with the task card or column UI, and add
the missing status control to TaskFormDialog or the relevant card component so
users can move tasks without dragging.
In `@src/features/manage-sprints/ui/SprintDeleteDialog.tsx`:
- Around line 13-26: Extract the repeated dialog setup from SprintDeleteDialog,
SprintFormDialog, and TaskFormDialog into a shared useNativeDialog hook under
src/shared, preserving showModal, cancel prevention, onClose invocation,
listener cleanup, and the onClose dependency. Replace each component’s local
ref/effect block with the shared hook and use its returned dialog ref.
- Around line 3-4: Remove the outdated UI-only and future-wiring comments from
SprintDeleteDialog.tsx, since SprintToolbar’s onConfirm already invokes the
useDeleteSprint mutation via deleteSprint.mutate(sprint.id). Do not change the
existing deletion wiring.
In `@src/features/manage-sprints/ui/SprintFormDialog.tsx`:
- Line 90: Review the autoFocus prop in SprintFormDialog and verify that
focusing the initial field on native modal open is intentional and accessible.
Preserve it if it matches the established TaskFormDialog pattern and does not
harm screen-reader behavior; otherwise replace it with the project’s existing
modal-focus approach.
- Around line 3-5: Remove the outdated “저장 로직 미배선” and follow-up onSubmit TODO
comments from SprintFormDialog.tsx, since SprintToolbar.handleSubmit already
routes create and update operations through the appropriate mutations. Keep the
existing UI shell comments that remain accurate.
In `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx`:
- Around line 7-12: Update the import from the task module in MyTasks.tsx so
Task is also marked as a type-only import, matching TaskStatus and the
convention used by Backlog.tsx; leave the runtime imports getMockSprintTasks and
TASK_STATUS unchanged.
In `@supabase/migrations/20260712220809_create_sprint_rpcs.sql`:
- Around line 1-8: Update the get_sprints RPC and the createSprint server action
to explicitly require public.is_workspace_member(p_workspace_id) before reading
or modifying workspace data. Reject unauthorized requests and preserve the
existing behavior for valid workspace members; do not rely solely on the
existing RLS policies while dev_full_access remains enabled.
---
Outside diff comments:
In `@src/features/manage-calendar/ui/CalendarView.tsx`:
- Around line 329-343: Update the 일정 이름 label and its associated input in the
CalendarView form to use a matching htmlFor and id, ensuring the label
explicitly references this title field without changing the existing validation
or input behavior.
In `@src/views/side-project/sprint-board/ui/SprintBoardView.tsx`:
- Around line 67-89: Update the loading flow in SprintBoardView so
tasksQuery.isPending or backlogQuery.isPending no longer returns before
rendering the sprint selector, toolbar, and summary header. Keep the surrounding
sprint UI visible, conditionally render only SprintBoard for the loading state,
and configure useSprintTasks with placeholderData to preserve previous task data
during sprint transitions and reduce flicker.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 86597ecf-fe80-42a2-b8a2-2abf4afe2535
📒 Files selected for processing (74)
src/app/workspaces/[workspaceId]/calendar/page.tsxsrc/app/workspaces/[workspaceId]/page.tsxsrc/app/workspaces/[workspaceId]/progress-chart/page.tsxsrc/app/workspaces/[workspaceId]/sprint-board/page.tsxsrc/entities/calendar-event/model/calendar-event.types.tssrc/entities/side-project/sprint/api/create-sprint.tssrc/entities/side-project/sprint/api/delete-sprint.tssrc/entities/side-project/sprint/api/get-sprints.tssrc/entities/side-project/sprint/api/update-sprint.tssrc/entities/side-project/sprint/api/use-create-sprint.tssrc/entities/side-project/sprint/api/use-delete-sprint.tssrc/entities/side-project/sprint/api/use-sprints.tssrc/entities/side-project/sprint/api/use-update-sprint.tssrc/entities/side-project/sprint/index.tssrc/entities/side-project/sprint/model/sprint.db.types.tssrc/entities/side-project/sprint/model/sprint.mapper.tssrc/entities/side-project/sprint/model/sprint.mock.tssrc/entities/side-project/sprint/model/sprint.schema.tssrc/entities/side-project/sprint/model/sprint.selectors.tssrc/entities/side-project/task/api/create-task.tssrc/entities/side-project/task/api/delete-task.tssrc/entities/side-project/task/api/get-backlog-tasks.tssrc/entities/side-project/task/api/get-sprint-tasks.tssrc/entities/side-project/task/api/update-task-sprint.tssrc/entities/side-project/task/api/update-task-status.tssrc/entities/side-project/task/api/update-task.tssrc/entities/side-project/task/api/use-backlog-tasks.tssrc/entities/side-project/task/api/use-create-task.tssrc/entities/side-project/task/api/use-delete-task.tssrc/entities/side-project/task/api/use-sprint-tasks.tssrc/entities/side-project/task/api/use-update-task-sprint.tssrc/entities/side-project/task/api/use-update-task-status.tssrc/entities/side-project/task/api/use-update-task.tssrc/entities/side-project/task/index.tssrc/entities/side-project/task/model/task.db.types.tssrc/entities/side-project/task/model/task.mapper.tssrc/entities/side-project/task/model/task.mock.tssrc/entities/side-project/task/model/task.schema.tssrc/entities/side-project/task/model/task.selectors.tssrc/entities/side-project/task/model/task.types.tssrc/features/manage-calendar/ui/CalendarView.tsxsrc/features/manage-sprint-tasks/index.tssrc/features/manage-sprint-tasks/lib/avatar-color.tssrc/features/manage-sprint-tasks/model/board-task.tssrc/features/manage-sprint-tasks/model/sprint-board-columns.tssrc/features/manage-sprint-tasks/model/task-form.tssrc/features/manage-sprint-tasks/model/use-sprint-board.tssrc/features/manage-sprint-tasks/model/use-task-dnd.tssrc/features/manage-sprint-tasks/ui/BacklogRow.tsxsrc/features/manage-sprint-tasks/ui/BacklogSection.tsxsrc/features/manage-sprint-tasks/ui/SprintBoard.tsxsrc/features/manage-sprint-tasks/ui/SprintColumn.tsxsrc/features/manage-sprint-tasks/ui/TaskCard.tsxsrc/features/manage-sprint-tasks/ui/TaskFormDialog.tsxsrc/features/manage-sprints/index.tssrc/features/manage-sprints/ui/SprintDeleteDialog.tsxsrc/features/manage-sprints/ui/SprintFormDialog.tsxsrc/features/manage-sprints/ui/SprintToolbar.tsxsrc/features/sprint-board/index.tssrc/features/sprint-board/model/task-form.tssrc/features/sprint-board/model/use-sprint-board.tssrc/shared/model/database.types.tssrc/views/side-project/progress-chart/index.tssrc/views/side-project/progress-chart/ui/ProgressChartView.tsxsrc/views/side-project/progress-chart/ui/ProgressStatRow.tsxsrc/views/side-project/progress-chart/ui/SprintProgressCard.tsxsrc/views/side-project/progress-chart/ui/StatusDonutChart.tsxsrc/views/side-project/progress-chart/ui/VelocityChart.tsxsrc/views/side-project/sprint-board/ui/SprintBoardView.tsxsrc/views/side-project/sprint-board/ui/SprintSelector.tsxsrc/widgets/side-project/dashboard-backlog/ui/Backlog.tsxsrc/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsxsrc/widgets/side-project/dashboard-velocity/ui/Velocity.tsxsupabase/migrations/20260712220809_create_sprint_rpcs.sql
💤 Files with no reviewable changes (3)
- src/features/sprint-board/index.ts
- src/features/sprint-board/model/use-sprint-board.ts
- src/features/sprint-board/model/task-form.ts
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/features/manage-calendar/ui/CalendarView.tsx (1)
329-343: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win라벨-인풋 연결 누락 (접근성)
일정 이름라벨이htmlFor/id또는 중첩 없이 입력과 분리되어 있어, 스크린리더 사용자가 어떤 입력과 연결된 라벨인지 알 수 없습니다. 정적 분석 도구에서도 동일하게 플래그되었습니다.♿ 제안 수정
- <label className="text-brand-muted block text-[12px] font-semibold"> + <label htmlFor="calendar-event-title" className="text-brand-muted block text-[12px] font-semibold"> 일정 이름 <span className="text-[`#ff6565`]">*</span> </label> <input + id="calendar-event-title" type="text"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/manage-calendar/ui/CalendarView.tsx` around lines 329 - 343, Update the 일정 이름 label and its associated input in the CalendarView form to use a matching htmlFor and id, ensuring the label explicitly references this title field without changing the existing validation or input behavior.Source: Linters/SAST tools
src/views/side-project/sprint-board/ui/SprintBoardView.tsx (1)
67-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win스프린트 전환 시 보드만 로딩 처리하세요
tasksQuery.isPending || backlogQuery.isPending에서 바로return해서, 새 스프린트를 처음 열 때SprintSelector/SprintToolbar/SprintSummaryHeader까지 같이 사라집니다.SprintBoard만 조건부로 감싸고,useSprintTasks에는placeholderData를 넣어 전환 깜빡임을 줄이는 편이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/views/side-project/sprint-board/ui/SprintBoardView.tsx` around lines 67 - 89, Update the loading flow in SprintBoardView so tasksQuery.isPending or backlogQuery.isPending no longer returns before rendering the sprint selector, toolbar, and summary header. Keep the surrounding sprint UI visible, conditionally render only SprintBoard for the loading state, and configure useSprintTasks with placeholderData to preserve previous task data during sprint transitions and reduce flicker.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/entities/side-project/sprint/api/update-sprint.ts`:
- Around line 13-26: Update updateSprint to require and apply a workspace_id
equality filter alongside the existing id filter when updating sprints, using
the authenticated workspace context rather than trusting an arbitrary client
value. Also remove the permissive dev_full_access RLS policy from the production
database configuration or migration.
In `@src/entities/side-project/task/api/create-task.ts`:
- Line 3: Update the task creation flow in create-task.ts to use the shared
getCurrentUserId() helper from current-user.ts instead of DEV_USER_ID when
populating created_by, preserving the existing validation and insert behavior so
development-only fallback remains centralized in the helper.
In `@src/entities/side-project/task/api/get-backlog-tasks.ts`:
- Around line 7-20: Update the database RLS configuration governing the tasks
query used by getBacklogTasks to remove or narrowly restrict the public.*
dev_full_access policy, ensuring access is limited to the authenticated user’s
workspace membership while preserving legitimate backlog reads.
In `@src/entities/side-project/task/api/use-update-task-status.ts`:
- Around line 30-34: Update the rollback loop in the mutation’s onError handler
to safely skip iteration when context or its previous snapshot is undefined,
while preserving restoration of every snapshot when available. Keep the existing
error toast behavior unchanged.
In `@src/entities/side-project/task/model/task.schema.ts`:
- Line 16: Update the assigneeId schema to use Zod’s standard z.uuid() validator
followed by .nullable(), replacing the current z.string().uuid() chain while
preserving nullable UUID validation.
In `@src/features/manage-calendar/ui/CalendarView.tsx`:
- Around line 353-365: Connect the “시간 (선택)” label to its input by adding a
matching htmlFor and id pair around the time field in CalendarView, using a
unique identifier and preserving the existing input behavior.
In `@src/features/manage-sprint-tasks/model/use-task-dnd.ts`:
- Around line 9-41: Extend useTaskDnd beyond native drag events by exposing a
keyboard- and single-pointer-accessible status-change action, such as a status
selector or change handler that invokes onMove with the task ID and target
TaskStatus. Integrate this alternative with the task card or column UI, and add
the missing status control to TaskFormDialog or the relevant card component so
users can move tasks without dragging.
In `@src/features/manage-sprints/ui/SprintDeleteDialog.tsx`:
- Around line 13-26: Extract the repeated dialog setup from SprintDeleteDialog,
SprintFormDialog, and TaskFormDialog into a shared useNativeDialog hook under
src/shared, preserving showModal, cancel prevention, onClose invocation,
listener cleanup, and the onClose dependency. Replace each component’s local
ref/effect block with the shared hook and use its returned dialog ref.
- Around line 3-4: Remove the outdated UI-only and future-wiring comments from
SprintDeleteDialog.tsx, since SprintToolbar’s onConfirm already invokes the
useDeleteSprint mutation via deleteSprint.mutate(sprint.id). Do not change the
existing deletion wiring.
In `@src/features/manage-sprints/ui/SprintFormDialog.tsx`:
- Line 90: Review the autoFocus prop in SprintFormDialog and verify that
focusing the initial field on native modal open is intentional and accessible.
Preserve it if it matches the established TaskFormDialog pattern and does not
harm screen-reader behavior; otherwise replace it with the project’s existing
modal-focus approach.
- Around line 3-5: Remove the outdated “저장 로직 미배선” and follow-up onSubmit TODO
comments from SprintFormDialog.tsx, since SprintToolbar.handleSubmit already
routes create and update operations through the appropriate mutations. Keep the
existing UI shell comments that remain accurate.
In `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx`:
- Around line 7-12: Update the import from the task module in MyTasks.tsx so
Task is also marked as a type-only import, matching TaskStatus and the
convention used by Backlog.tsx; leave the runtime imports getMockSprintTasks and
TASK_STATUS unchanged.
In `@supabase/migrations/20260712220809_create_sprint_rpcs.sql`:
- Around line 1-8: Update the get_sprints RPC and the createSprint server action
to explicitly require public.is_workspace_member(p_workspace_id) before reading
or modifying workspace data. Reject unauthorized requests and preserve the
existing behavior for valid workspace members; do not rely solely on the
existing RLS policies while dev_full_access remains enabled.
---
Outside diff comments:
In `@src/features/manage-calendar/ui/CalendarView.tsx`:
- Around line 329-343: Update the 일정 이름 label and its associated input in the
CalendarView form to use a matching htmlFor and id, ensuring the label
explicitly references this title field without changing the existing validation
or input behavior.
In `@src/views/side-project/sprint-board/ui/SprintBoardView.tsx`:
- Around line 67-89: Update the loading flow in SprintBoardView so
tasksQuery.isPending or backlogQuery.isPending no longer returns before
rendering the sprint selector, toolbar, and summary header. Keep the surrounding
sprint UI visible, conditionally render only SprintBoard for the loading state,
and configure useSprintTasks with placeholderData to preserve previous task data
during sprint transitions and reduce flicker.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 86597ecf-fe80-42a2-b8a2-2abf4afe2535
📒 Files selected for processing (74)
src/app/workspaces/[workspaceId]/calendar/page.tsxsrc/app/workspaces/[workspaceId]/page.tsxsrc/app/workspaces/[workspaceId]/progress-chart/page.tsxsrc/app/workspaces/[workspaceId]/sprint-board/page.tsxsrc/entities/calendar-event/model/calendar-event.types.tssrc/entities/side-project/sprint/api/create-sprint.tssrc/entities/side-project/sprint/api/delete-sprint.tssrc/entities/side-project/sprint/api/get-sprints.tssrc/entities/side-project/sprint/api/update-sprint.tssrc/entities/side-project/sprint/api/use-create-sprint.tssrc/entities/side-project/sprint/api/use-delete-sprint.tssrc/entities/side-project/sprint/api/use-sprints.tssrc/entities/side-project/sprint/api/use-update-sprint.tssrc/entities/side-project/sprint/index.tssrc/entities/side-project/sprint/model/sprint.db.types.tssrc/entities/side-project/sprint/model/sprint.mapper.tssrc/entities/side-project/sprint/model/sprint.mock.tssrc/entities/side-project/sprint/model/sprint.schema.tssrc/entities/side-project/sprint/model/sprint.selectors.tssrc/entities/side-project/task/api/create-task.tssrc/entities/side-project/task/api/delete-task.tssrc/entities/side-project/task/api/get-backlog-tasks.tssrc/entities/side-project/task/api/get-sprint-tasks.tssrc/entities/side-project/task/api/update-task-sprint.tssrc/entities/side-project/task/api/update-task-status.tssrc/entities/side-project/task/api/update-task.tssrc/entities/side-project/task/api/use-backlog-tasks.tssrc/entities/side-project/task/api/use-create-task.tssrc/entities/side-project/task/api/use-delete-task.tssrc/entities/side-project/task/api/use-sprint-tasks.tssrc/entities/side-project/task/api/use-update-task-sprint.tssrc/entities/side-project/task/api/use-update-task-status.tssrc/entities/side-project/task/api/use-update-task.tssrc/entities/side-project/task/index.tssrc/entities/side-project/task/model/task.db.types.tssrc/entities/side-project/task/model/task.mapper.tssrc/entities/side-project/task/model/task.mock.tssrc/entities/side-project/task/model/task.schema.tssrc/entities/side-project/task/model/task.selectors.tssrc/entities/side-project/task/model/task.types.tssrc/features/manage-calendar/ui/CalendarView.tsxsrc/features/manage-sprint-tasks/index.tssrc/features/manage-sprint-tasks/lib/avatar-color.tssrc/features/manage-sprint-tasks/model/board-task.tssrc/features/manage-sprint-tasks/model/sprint-board-columns.tssrc/features/manage-sprint-tasks/model/task-form.tssrc/features/manage-sprint-tasks/model/use-sprint-board.tssrc/features/manage-sprint-tasks/model/use-task-dnd.tssrc/features/manage-sprint-tasks/ui/BacklogRow.tsxsrc/features/manage-sprint-tasks/ui/BacklogSection.tsxsrc/features/manage-sprint-tasks/ui/SprintBoard.tsxsrc/features/manage-sprint-tasks/ui/SprintColumn.tsxsrc/features/manage-sprint-tasks/ui/TaskCard.tsxsrc/features/manage-sprint-tasks/ui/TaskFormDialog.tsxsrc/features/manage-sprints/index.tssrc/features/manage-sprints/ui/SprintDeleteDialog.tsxsrc/features/manage-sprints/ui/SprintFormDialog.tsxsrc/features/manage-sprints/ui/SprintToolbar.tsxsrc/features/sprint-board/index.tssrc/features/sprint-board/model/task-form.tssrc/features/sprint-board/model/use-sprint-board.tssrc/shared/model/database.types.tssrc/views/side-project/progress-chart/index.tssrc/views/side-project/progress-chart/ui/ProgressChartView.tsxsrc/views/side-project/progress-chart/ui/ProgressStatRow.tsxsrc/views/side-project/progress-chart/ui/SprintProgressCard.tsxsrc/views/side-project/progress-chart/ui/StatusDonutChart.tsxsrc/views/side-project/progress-chart/ui/VelocityChart.tsxsrc/views/side-project/sprint-board/ui/SprintBoardView.tsxsrc/views/side-project/sprint-board/ui/SprintSelector.tsxsrc/widgets/side-project/dashboard-backlog/ui/Backlog.tsxsrc/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsxsrc/widgets/side-project/dashboard-velocity/ui/Velocity.tsxsupabase/migrations/20260712220809_create_sprint_rpcs.sql
💤 Files with no reviewable changes (3)
- src/features/sprint-board/index.ts
- src/features/sprint-board/model/use-sprint-board.ts
- src/features/sprint-board/model/task-form.ts
🛑 Comments failed to post (13)
src/entities/side-project/sprint/api/update-sprint.ts (1)
13-26: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # sprints 테이블에 대한 RLS 정책 확인 rg -n "sprints" supabase/migrations -l rg -n "policy|RLS|ENABLE ROW LEVEL SECURITY" supabase/migrations -i -A5 | rg -B5 -A5 "sprints"Repository: TeampleRun/syncly
Length of output: 1489
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== migrations mentioning sprints ==\n' rg -n "sprints|ENABLE ROW LEVEL SECURITY|CREATE POLICY|ALTER TABLE.*ROW LEVEL SECURITY|workspace_members|auth.uid" supabase/migrations -i -A4 -B4 printf '\n== sprints table definition ==\n' sed -n '1,260p' supabase/migrations/20260709025327_create_enums_and_core_tables.sql printf '\n== auth RLS policies migration ==\n' sed -n '1,260p' supabase/migrations/20260709025820_add_auth_rls_policies.sql printf '\n== sprint RPCs migration ==\n' sed -n '1,240p' supabase/migrations/20260712220809_create_sprint_rpcs.sql printf '\n== updateSprint implementation and callers ==\n' sed -n '1,220p' src/entities/side-project/sprint/api/update-sprint.ts rg -n "updateSprint\\(" src -A3 -B3Repository: TeampleRun/syncly
Length of output: 46072
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== dev_full_access references ==\n' rg -n "dev_full_access|drop policy|drop all policies|disable row level security|enable row level security" supabase/migrations -i -A3 -B3 printf '\n== any later sprint policy changes ==\n' rg -n "sprints_member_all|create policy.*sprints|drop policy.*sprints" supabase/migrations -i -A3 -B3 printf '\n== updateSprint call sites with workspace context ==\n' rg -n "updateSprint\\(" src -A5 -B5Repository: TeampleRun/syncly
Length of output: 2397
workspace_id기준 스코프를 추가하세요.
지금은public.sprints에dev_full_accessRLS가 남아 있어id만 알면 다른 워크스페이스 스프린트도 수정됩니다.updateSprint에workspace_id조건을 넣고, 운영 환경에선 이 permissive 정책을 제거해야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/entities/side-project/sprint/api/update-sprint.ts` around lines 13 - 26, Update updateSprint to require and apply a workspace_id equality filter alongside the existing id filter when updating sprints, using the authenticated workspace context rather than trusting an arbitrary client value. Also remove the permissive dev_full_access RLS policy from the production database configuration or migration.src/entities/side-project/task/api/create-task.ts (1)
3-3: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # DEV_USER_ID 사용처와 실제 인증(세션) 연동 여부 확인 rg -n "DEV_USER_ID" --type=ts rg -n "auth.getUser|getSession" --type=ts -g '!**/node_modules/**'Repository: TeampleRun/syncly
Length of output: 155
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n## target file\n' if [ -f src/entities/side-project/task/api/create-task.ts ]; then wc -l src/entities/side-project/task/api/create-task.ts cat -n src/entities/side-project/task/api/create-task.ts else echo "missing target file" fi printf '\n## nearby task api files\n' fd -a 'create-task.ts' src || true fd -a 'task' src/entities/side-project || true printf '\n## search for relevant symbols\n' rg -n "DEV_USER_ID|created_by|createdBy|auth\\.getUser|auth\\.getSession|getSession\\(|user\\.id|session\\.user|supabase\\.auth" srcRepository: TeampleRun/syncly
Length of output: 6698
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n## current-user helper\n' wc -l src/shared/api/supabase/current-user.ts cat -n src/shared/api/supabase/current-user.ts printf '\n## actions using getCurrentUserId\n' rg -n "getCurrentUserId\(" src/entities src/shared printf '\n## DEV_USER_ID direct usages around create/update actions\n' rg -n "DEV_USER_ID" src/entities/side-project src/entities/work-schedule src/entities/workspace src/shared/configRepository: TeampleRun/syncly
Length of output: 2491
create-task.ts에서DEV_USER_ID대신getCurrentUserId()를 사용하세요.주석과 달리 현재 생성자(
created_by)가 실제 로그인 유저가 아니라 고정DEV_USER_ID로 저장됩니다.src/shared/api/supabase/current-user.ts의 공용 헬퍼로 바꿔 개발 환경만 fallback 되게 맞춰야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/entities/side-project/task/api/create-task.ts` at line 3, Update the task creation flow in create-task.ts to use the shared getCurrentUserId() helper from current-user.ts instead of DEV_USER_ID when populating created_by, preserving the existing validation and insert behavior so development-only fallback remains centralized in the helper.src/entities/side-project/task/api/get-backlog-tasks.ts (1)
7-20: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # tasks 테이블 RLS 정책 확인 rg -n "tasks" supabase/migrations --iglob '*.sql' -A3 -B3 | rg -n "policy|rls" -iRepository: TeampleRun/syncly
Length of output: 978
🏁 Script executed:
#!/bin/bash set -euo pipefail # 1) tasks 관련 RLS 정책 정의 확인 sed -n '1,220p' supabase/migrations/20260709025820_add_auth_rls_policies.sql # 2) tasks 테이블에서 select 정책이 별도로 정의됐는지 확인 rg -n "create policy|policy .*tasks|tasks" supabase/migrations --iglob '*.sql' -A4 -B4Repository: TeampleRun/syncly
Length of output: 26466
🏁 Script executed:
#!/bin/bash set -euo pipefail # 1) dev_full_access가 제거되는지 확인 rg -n "drop policy .*dev_full_access|dev_full_access" supabase/migrations --iglob '*.sql' -A3 -B3 # 2) 브라우저용 Supabase 클라이언트가 anon/publishable key를 쓰는지 확인 rg -n "getSupabaseBrowserClient|createBrowserClient|NEXT_PUBLIC_SUPABASE|publishable|anon" src supabase --iglob '*.{ts,tsx,ts,mts,cts,js,mjs,cjs,sql}' -A2 -B2Repository: TeampleRun/syncly
Length of output: 11559
브라우저 조회는 현재
dev_full_access때문에 워크스페이스 경계가 막히지 않습니다.
tasks에는 멤버십 RLS가 추가돼 있지만,public.*전체에 적용된dev_full_access가 아직 남아 있어 이 조회로 다른 워크스페이스 데이터가 읽힐 수 있습니다. 이 정책을 제거하거나 더 좁은 권한으로 제한해야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/entities/side-project/task/api/get-backlog-tasks.ts` around lines 7 - 20, Update the database RLS configuration governing the tasks query used by getBacklogTasks to remove or narrowly restrict the public.* dev_full_access policy, ensuring access is limited to the authenticated user’s workspace membership while preserving legitimate backlog reads.src/entities/side-project/task/api/use-update-task-status.ts (1)
30-34: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
옵셔널 체이닝 누락으로 롤백 시 런타임 에러 위험.
context?.previous.forEach(...)에서?.는previous접근만 보호하며,context가undefined일 경우context?.previous는undefined가 되고 이어지는.forEach(...)호출에서TypeError가 발생합니다.onMutate에서cancelQueries가 실패하는 등 예외가 던져지면context가 정의되지 않은 채로onError가 호출될 수 있습니다.🐛 제안 수정
- context?.previous.forEach(([key, data]) => queryClient.setQueryData(key, data)); + context?.previous.forEach(([key, data]) => queryClient.setQueryData(key, data)); // 기존 유지 시 아래로 대체 + context?.previous?.forEach(([key, data]) => queryClient.setQueryData(key, data));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/entities/side-project/task/api/use-update-task-status.ts` around lines 30 - 34, Update the rollback loop in the mutation’s onError handler to safely skip iteration when context or its previous snapshot is undefined, while preserving restoration of every snapshot when available. Keep the existing error toast behavior unchanged.src/entities/side-project/task/model/task.schema.ts (1)
16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
🧩 Analysis chain
🌐 Web query:
Zod 4 z.string().uuid() deprecated in favor of z.uuid()?💡 Result:
Yes, in Zod 4, the method z.string.uuid is deprecated in favor of the top-level z.uuid [1][2]. In Zod 4, common string validation methods like.email,.url, and.uuid have been moved from the ZodString prototype to the top-level z namespace [1]. While the previous method-chaining syntax (e.g., z.string.uuid) continues to work, it is considered deprecated in favor of the new, more tree-shakable and concise top-level API [1]. Additionally, it is important to note that Zod 4 introduced a breaking change regarding UUID validation behavior. The new z.uuid strictly enforces RFC 9562/4122 compliance, meaning it will reject some UUIDs that were accepted by Zod 3 [3][4][1]. If you require a more permissive validator that accepts any 8-4-4-4-12 hex pattern (similar to the legacy behavior), you should use z.guid instead [3][1].
Citations:
- 1: https://github.com/colinhacks/zod/blob/c7805073/packages/docs/content/v4/changelog.mdx
- 2: colinhacks/zod#5205
- 3: https://gist.github.com/imaman/a62d1c7bab770a3b49fe3be10a66f48a
- 4: colinhacks/zod#4969
🏁 Script executed:
#!/bin/bash set -euo pipefail # 확인: Zod 버전과 해당 스키마 사용 맥락을 봅니다. git ls-files package.json pnpm-lock.yaml package-lock.json yarn.lock src/entities/side-project/task/model/task.schema.ts \ | xargs -r -I{} sh -c 'echo "=== {} ==="; sed -n "1,220p" "{}"; echo'Repository: TeampleRun/syncly
Length of output: 10820
🏁 Script executed:
#!/bin/bash set -euo pipefail # 확인: Zod 버전과 해당 스키마 사용 맥락을 봅니다. for f in package.json pnpm-lock.yaml package-lock.json yarn.lock src/entities/side-project/task/model/task.schema.ts; do if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then echo "=== $f ===" sed -n '1,220p' "$f" echo fi doneRepository: TeampleRun/syncly
Length of output: 10820
z.string().uuid()대신z.uuid()로 바꾸세요.
Zod 4.4.3에서는 UUID 검증의 표준 API가z.uuid()라서, 이 필드는z.uuid().nullable()로 쓰면 됩니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/entities/side-project/task/model/task.schema.ts` at line 16, Update the assigneeId schema to use Zod’s standard z.uuid() validator followed by .nullable(), replacing the current z.string().uuid() chain while preserving nullable UUID validation.src/features/manage-calendar/ui/CalendarView.tsx (1)
353-365: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
라벨-인풋 연결 누락 (접근성)
시간 (선택)라벨도 동일하게 대응하는 입력과 연결되어 있지 않습니다.htmlFor/id로 연결하면 353, 356 두 정적 분석 경고를 모두 해소할 수 있습니다.♿ 제안 수정
- <label className="text-brand-muted block text-[12px] font-semibold"> + <label htmlFor="calendar-event-time" className="text-brand-muted block text-[12px] font-semibold"> 시간 (선택) </label> <input + id="calendar-event-time" type="text"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<label htmlFor="calendar-event-time" className="text-brand-muted block text-[12px] font-semibold"> 시간 (선택) </label> <input id="calendar-event-time" type="text" value={formValues.time} onChange={(event) => setFormValues((current) => ({ ...current, time: event.target.value })) } placeholder="예: 오후 3:00" className="text-brand-ink focus:ring-brand mt-2 h-11 w-full rounded-[16px] bg-[`#f1f3fb`] px-4 text-[14px] transition outline-none placeholder:text-[`#a8afc8`] focus:ring-1" /> </div>🧰 Tools
🪛 React Doctor (0.5.8)
[warning] 353-353: Screen reader users can't tell which input this label names because it's tied to none, so add
htmlForor wrap the input inside it.Tie every label to a control with
htmlFor, or by nesting the input.(label-has-associated-control)
[warning] 356-356: Blind users can't tell what this control does because screen readers find no label, so add visible text,
aria-label, oraria-labelledby.Give every interactive control a label screen readers can read.
(control-has-associated-label)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/manage-calendar/ui/CalendarView.tsx` around lines 353 - 365, Connect the “시간 (선택)” label to its input by adding a matching htmlFor and id pair around the time field in CalendarView, using a unique identifier and preserving the existing input behavior.Source: Linters/SAST tools
src/features/manage-sprint-tasks/model/use-task-dnd.ts (1)
9-41: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # Check whether TaskFormDialog/task-form.ts expose a status field as a drag alternative fd -e ts -e tsx . src/features/manage-sprint-tasks | xargs rg -n -i 'status' -C3Repository: TeampleRun/syncly
Length of output: 14514
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== files ==" git ls-files 'src/features/manage-sprint-tasks/**' | sed -n '1,200p' echo echo "== outline task-form.ts ==" ast-grep outline src/features/manage-sprint-tasks/model/task-form.ts --view expanded || true echo echo "== task-form.ts relevant lines ==" sed -n '1,220p' src/features/manage-sprint-tasks/model/task-form.ts echo echo "== SprintColumn.tsx relevant lines ==" sed -n '1,240p' src/features/manage-sprint-tasks/ui/SprintColumn.tsx echo echo "== TaskCard.tsx relevant lines ==" sed -n '1,260p' src/features/manage-sprint-tasks/ui/TaskCard.tsx echo echo "== SprintBoard.tsx relevant lines ==" sed -n '1,220p' src/features/manage-sprint-tasks/ui/SprintBoard.tsxRepository: TeampleRun/syncly
Length of output: 11033
상태 변경에 키보드/단일 포인터 대안을 추가하세요
src/features/manage-sprint-tasks/model/use-task-dnd.ts의 상태 변경은 네이티브 드래그에만 연결돼 있고,TaskFormDialog에도 status 필드가 없어 카드 이동을 드래그 없이 할 수 없습니다. 카드나 컬럼에 상태 변경 버튼/셀렉트 같은 대안을 두어야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/manage-sprint-tasks/model/use-task-dnd.ts` around lines 9 - 41, Extend useTaskDnd beyond native drag events by exposing a keyboard- and single-pointer-accessible status-change action, such as a status selector or change handler that invokes onMove with the task ID and target TaskStatus. Integrate this alternative with the task card or column UI, and add the missing status control to TaskFormDialog or the relevant card component so users can move tasks without dragging.src/features/manage-sprints/ui/SprintDeleteDialog.tsx (2)
3-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
TODO 주석이 실제 배선 상태와 불일치
"UI 전용 셸(삭제 로직 미배선)"이라는 주석과 TODO가 남아있지만,
SprintToolbar.tsx의onConfirm={() => deleteSprint.mutate(sprint.id)}(91번째 줄)에서 이미useDeleteSprint뮤테이션에 연결되어 있습니다. 오래된 주석은 향후 혼동이나 중복 작업을 유발할 수 있으니 정리해주세요.📝 주석 정리 제안
-// 스프린트 삭제 확인 모달 — UI 전용 셸(삭제 로직 미배선). -// TODO(후속): onConfirm을 스프린트 삭제 서버액션(useMutation)에 연결한다. +// 스프린트 삭제 확인 모달 — onConfirm은 SprintToolbar에서 useDeleteSprint 뮤테이션으로 연결된다.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.// 스프린트 삭제 확인 모달 — onConfirm은 SprintToolbar에서 useDeleteSprint 뮤테이션으로 연결된다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/manage-sprints/ui/SprintDeleteDialog.tsx` around lines 3 - 4, Remove the outdated UI-only and future-wiring comments from SprintDeleteDialog.tsx, since SprintToolbar’s onConfirm already invokes the useDeleteSprint mutation via deleteSprint.mutate(sprint.id). Do not change the existing deletion wiring.
13-26: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
다이얼로그 오픈/취소 처리 로직 중복 — 공용 훅 추출 제안
이
useEffect(showModal + cancel 리스너) 블록이SprintFormDialog.tsx(47-57)와 기존TaskFormDialog.tsx(58-70)에도 그대로 반복됩니다.useNativeDialog(onClose)같은 공용 훅으로 추출해src/shared/lib에 두는 것을 제안합니다.♻️ 공용 훅 추출 예시
// src/shared/lib/use-native-dialog.ts import { useEffect, useRef } from 'react'; export function useNativeDialog(onClose: () => void) { const dialogRef = useRef<HTMLDialogElement>(null); useEffect(() => { const dialog = dialogRef.current; if (!dialog) return undefined; dialog.showModal(); const handleCancel = (event: Event) => { event.preventDefault(); onClose(); }; dialog.addEventListener('cancel', handleCancel); return () => dialog.removeEventListener('cancel', handleCancel); }, [onClose]); return dialogRef; }- const dialogRef = useRef<HTMLDialogElement>(null); - - useEffect(() => { - const dialog = dialogRef.current; - if (!dialog) return undefined; - dialog.showModal(); - const handleCancel = (event: Event) => { - event.preventDefault(); - onClose(); - }; - dialog.addEventListener('cancel', handleCancel); - return () => dialog.removeEventListener('cancel', handleCancel); - }, [onClose]); + const dialogRef = useNativeDialog(onClose);As per coding guidelines, "
src/shared/**/*.{ts,tsx}: Place reusable common code insrc/shared, including UI components and libraries."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/manage-sprints/ui/SprintDeleteDialog.tsx` around lines 13 - 26, Extract the repeated dialog setup from SprintDeleteDialog, SprintFormDialog, and TaskFormDialog into a shared useNativeDialog hook under src/shared, preserving showModal, cancel prevention, onClose invocation, listener cleanup, and the onClose dependency. Replace each component’s local ref/effect block with the shared hook and use its returned dialog ref.Source: Coding guidelines
src/features/manage-sprints/ui/SprintFormDialog.tsx (2)
3-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
TODO 주석이 실제 배선 상태와 불일치
"저장 로직 미배선" TODO가 남아있지만,
SprintToolbar.tsx의handleSubmit(39-45번째 줄)에서updateSprint.mutate(...)/createSprint.mutate(...)로 이미 연결되어 있습니다.SprintDeleteDialog.tsx와 동일한 패턴이니 함께 정리해주세요.📝 주석 정리 제안
-// 스프린트 생성/수정 모달 — UI 전용 셸(저장 로직 미배선). -// 톤은 TaskFormDialog와 동일(네이티브 <dialog>.showModal, rounded-2xl 패널, 슬레이트 입력). -// TODO(후속): onSubmit을 스프린트 생성/수정 서버액션(useMutation)에 연결한다. +// 스프린트 생성/수정 모달 — 톤은 TaskFormDialog와 동일(네이티브 <dialog>.showModal, rounded-2xl 패널, 슬레이트 입력). +// onSubmit은 SprintToolbar에서 useCreateSprint/useUpdateSprint 뮤테이션으로 연결된다.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.// 스프린트 생성/수정 모달 — 톤은 TaskFormDialog와 동일(네이티브 <dialog>.showModal, rounded-2xl 패널, 슬레이트 입력). // onSubmit은 SprintToolbar에서 useCreateSprint/useUpdateSprint 뮤테이션으로 연결된다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/manage-sprints/ui/SprintFormDialog.tsx` around lines 3 - 5, Remove the outdated “저장 로직 미배선” and follow-up onSubmit TODO comments from SprintFormDialog.tsx, since SprintToolbar.handleSubmit already routes create and update operations through the appropriate mutations. Keep the existing UI shell comments that remain accurate.
90-90: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
정적 분석:
autoFocus사용 경고 — 모달 컨텍스트에서는 참고용React Doctor(
no-autofocus)가autoFocus를 경고했습니다. 다만 네이티브<dialog>.showModal()안에서 첫 입력 필드에 초기 포커스를 주는 것은 WAI-ARIA 모달 다이얼로그 패턴에서 흔히 권장되는 방식이며, 같은 코드베이스의TaskFormDialog.tsx도 동일하게 사용 중입니다. 다만 스크린리더 사용자 경험에 문제가 없는지 한 번 확인해보시는 것을 권장합니다.🧰 Tools
🪛 React Doctor (0.5.8)
[warning] 90-90:
autoFocusmoves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.Do not use
autoFocus. It disorients users on load.(no-autofocus)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/manage-sprints/ui/SprintFormDialog.tsx` at line 90, Review the autoFocus prop in SprintFormDialog and verify that focusing the initial field on native modal open is intentional and accessible. Preserve it if it matches the established TaskFormDialog pattern and does not harm screen-reader behavior; otherwise replace it with the project’s existing modal-focus approach.Source: Linters/SAST tools
src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx (1)
7-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
타입 전용 import 스타일 불일치
같은 모듈에서
TaskStatus는type접두사를 사용하지만Task는 사용하지 않습니다 (Backlog.tsx에서는type Task로 일관되게 사용).verbatimModuleSyntax가 활성화된 경우 타입만 사용되는 식별자에type접두사가 없으면 빌드/린트 오류가 날 수 있습니다.🔧 제안 수정
import { getMockSprintTasks, - Task, + type Task, TASK_STATUS, type TaskStatus, } from '`@/entities/side-project/task`';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.import { getMockSprintTasks, type Task, TASK_STATUS, type TaskStatus, } from '`@/entities/side-project/task`';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx` around lines 7 - 12, Update the import from the task module in MyTasks.tsx so Task is also marked as a type-only import, matching TaskStatus and the convention used by Backlog.tsx; leave the runtime imports getMockSprintTasks and TASK_STATUS unchanged.supabase/migrations/20260712220809_create_sprint_rpcs.sql (1)
1-8: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== locate relevant files ==" git ls-files | rg 'supabase/migrations/20260712220809_create_sprint_rpcs.sql|src/entities/side-project/sprint/api/create-sprint.ts|get-sprints.ts|supabase/.+sql$|migrations/.+sql$' || true echo echo "== outline migration file ==" ast-grep outline supabase/migrations/20260712220809_create_sprint_rpcs.sql --view expanded || true echo echo "== outline create-sprint api file if present ==" fd -a 'create-sprint.ts' src || true echo echo "== search for sprint RLS/policies/functions ==" rg -n --hidden -S "create policy|alter table .* enable row level security|get_sprints|create_sprint|workspace_id|auth\.uid\(\)" supabase src | sed -n '1,240p'Repository: TeampleRun/syncly
Length of output: 28349
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== file stats ==" for f in supabase/migrations/20260712220809_create_sprint_rpcs.sql src/entities/side-project/sprint/api/create-sprint.ts; do if [ -f "$f" ]; then wc -l "$f" fi done echo echo "== migration excerpt ==" sed -n '1,220p' supabase/migrations/20260712220809_create_sprint_rpcs.sql echo echo "== create-sprint excerpt ==" if [ -f src/entities/side-project/sprint/api/create-sprint.ts ]; then sed -n '1,220p' src/entities/side-project/sprint/api/create-sprint.ts fi echo echo "== migrations mentioning sprints/tasks policies ==" rg -n --hidden -S "enable row level security|create policy|sprints|tasks" supabase/migrations | sed -n '1,260p'Repository: TeampleRun/syncly
Length of output: 8726
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== inspect auth RLS migration around sprints/tasks ==" sed -n '1,180p' supabase/migrations/20260709025820_add_auth_rls_policies.sql echo echo "== search for dev_full_access removal or policy replacement ==" rg -n --hidden -S "dev_full_access|drop policy|alter policy|sprints_select_member|tasks_select_member" supabase/migrations | sed -n '1,240p'Repository: TeampleRun/syncly
Length of output: 4702
🏁 Script executed:
#!/bin/bash set -euo pipefail python3 - <<'PY' from pathlib import Path p = Path('supabase/migrations/20260709025820_add_auth_rls_policies.sql') text = p.read_text() for needle in ['sprints','tasks','dev_full_access','create policy']: print(f'-- {needle} --') for i, line in enumerate(text.splitlines(), 1): if needle in line: print(f'{i}: {line}') PYRepository: TeampleRun/syncly
Length of output: 1718
RPC/서버액션에서 워크스페이스 멤버십을 직접 확인해야 합니다
sprints/tasks용 RLS 정책은 이미 있지만,dev_full_access가 살아 있는 동안에는get_sprints와createSprint가p_workspace_id만 믿고 다른 워크스페이스를 조회/수정할 수 있습니다.public.is_workspace_member(p_workspace_id)가드를 이 경로에 추가하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@supabase/migrations/20260712220809_create_sprint_rpcs.sql` around lines 1 - 8, Update the get_sprints RPC and the createSprint server action to explicitly require public.is_workspace_member(p_workspace_id) before reading or modifying workspace data. Reject unauthorized requests and preserve the existing behavior for valid workspace members; do not rely solely on the existing RLS policies while dev_full_access remains enabled.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/entities/side-project/task/api/create-task.ts`:
- Around line 27-29: createTask 주변에서 Supabase 서버 클라이언트가 중복 생성되지 않도록 정리하세요. 먼저
생성된 supabase 클라이언트를 재사용할 수 있게 current-user.ts의 getCurrentUserId 시그니처와 구현을 수정하고,
createTask의 호출부 및 다른 getCurrentUserId 호출부(use-create-task.ts 등)를 새 인자 계약에 맞게
업데이트하세요.
In `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx`:
- Line 9: MyTasks가 getMockSprintTasks를 사용하지 않도록 제거하고, useSprintTasks 등 기존 React
Query 경로에서 조회한 실제 Supabase 스프린트 태스크를 사용하도록 복원하세요. 스프린트 보드의 생성·수정·상태 변경이 대시보드에
반영되도록 해당 훅의 데이터와 로딩·빈 상태 처리 흐름을 유지하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c9a0f37a-31f7-46fa-8a66-d778131bde96
📒 Files selected for processing (6)
src/app/workspaces/[workspaceId]/progress-chart/page.tsxsrc/entities/side-project/task/api/create-task.tssrc/features/manage-sprints/ui/SprintDeleteDialog.tsxsrc/features/manage-sprints/ui/SprintFormDialog.tsxsrc/views/progress-chart/ui/ProgressChartPage.tsxsrc/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx
💤 Files with no reviewable changes (2)
- src/features/manage-sprints/ui/SprintDeleteDialog.tsx
- src/features/manage-sprints/ui/SprintFormDialog.tsx
| const supabase = await createSupabaseServerClient(); | ||
| const createdBy = await getCurrentUserId(); | ||
| const payload = toTaskInsert(parsed.data, { workspaceId, sprintId, createdBy }); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value
Supabase 서버 클라이언트가 중복 생성됩니다.
Line 27에서 createSupabaseServerClient()로 클라이언트를 생성한 뒤, Line 28의 getCurrentUserId()가 내부적으로 또 한 번 createSupabaseServerClient()를 호출합니다(제공된 그래프 컨텍스트 참조: getCurrentUserId는 자체적으로 await createSupabaseServerClient()를 수행). 기능상 문제는 없지만 요청당 클라이언트가 두 번 생성되어 불필요한 오버헤드가 발생합니다.
♻️ 개선 제안 — `getCurrentUserId`가 클라이언트를 인자로 받도록 리팩터링(선택)
- const supabase = await createSupabaseServerClient();
- const createdBy = await getCurrentUserId();
+ const supabase = await createSupabaseServerClient();
+ const createdBy = await getCurrentUserId(supabase);이 변경은 src/shared/api/supabase/current-user.ts의 시그니처 변경이 필요하므로, 다른 호출부(use-create-task.ts 등)에 영향이 없는지 함께 확인해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/entities/side-project/task/api/create-task.ts` around lines 27 - 29,
createTask 주변에서 Supabase 서버 클라이언트가 중복 생성되지 않도록 정리하세요. 먼저 생성된 supabase 클라이언트를
재사용할 수 있게 current-user.ts의 getCurrentUserId 시그니처와 구현을 수정하고, createTask의 호출부 및 다른
getCurrentUserId 호출부(use-create-task.ts 등)를 새 인자 계약에 맞게 업데이트하세요.
| import { getSprintTasks, Task, TASK_STATUS, type TaskStatus } from '@/entities/side-project/task'; | ||
| import { | ||
| getMockSprintTasks, | ||
| type Task, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
대시보드 태스크를 Mock 데이터로 되돌리지 마세요.
이 변경으로 MyTasks가 Supabase의 실제 스프린트 태스크 대신 getMockSprintTasks를 계속 사용합니다. 따라서 스프린트 보드에서 생성·수정·상태 변경한 내용이 대시보드에 반영되지 않고, PR 목표인 대시보드 데이터 소스의 실 DB 전환도 충족하지 못합니다. useSprintTasks 등 기존 React Query 경로에서 조회한 데이터를 사용하도록 복원하세요.
Also applies to: 21-21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx` at line 9,
MyTasks가 getMockSprintTasks를 사용하지 않도록 제거하고, useSprintTasks 등 기존 React Query 경로에서
조회한 실제 Supabase 스프린트 태스크를 사용하도록 복원하세요. 스프린트 보드의 생성·수정·상태 변경이 대시보드에 반영되도록 해당 훅의
데이터와 로딩·빈 상태 처리 흐름을 유지하세요.
There was a problem hiding this comment.
실 DB 연동 대상이라면 조회 실패 또는 빈 데이터 상황에서 목업으로 fallback하지 않는 방향이 맞을 거 같습니다. useSprintTasks와 같은 React Query 기반 조회 결과를 사용하고 로딩 중, 조회 실패, 정상 조회 시처럼 구분하여 표시하면 좋을 거 같습니다
There was a problem hiding this comment.
@seongjinss555, 전적으로 동의합니다. 실 DB 연동 대상이라면 조회 실패나 빈 데이터를 목업으로 감추는 대신, useSprintTasks 결과의 isPending/isError/데이터 유무를 명시적으로 구분해서 보여주는 게 맞습니다. 예를 들면:
const { data: sprintTasks, isPending, isError } = useSprintTasks(currentSprint?.id);
if (isPending) return <WidgetCard>{header}<LoadingState /></WidgetCard>;
if (isError) return <WidgetCard>{header}<ErrorState /></WidgetCard>;
if (!sprintTasks || sprintTasks.length === 0) return <WidgetCard>{header}<EmptyState /></WidgetCard>;이렇게 하면 스프린트 보드에서 생성·수정·상태 변경한 내용이 대시보드에도 즉시 반영되고, 실패/빈 상태를 목업으로 가리는 문제도 없어집니다.
✏️ Learnings added
Learnt from: seongjinss555
Repo: TeampleRun/syncly PR: 44
File: src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx:9-9
Timestamp: 2026-07-13T06:28:33.452Z
Learning: In `src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx` (and similar dashboard widgets consuming Supabase-backed entities like `@/entities/side-project/task`), do not fall back to mock data (e.g., `getMockSprintTasks`) when a React Query hook such as `useSprintTasks` returns a loading, error, or empty state. Instead, use the query's `isPending`/`isError`/data states to render distinct loading, error, and empty UI, so dashboard widgets always reflect real Supabase data.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/views/progress-chart/ui/ProgressChartPage.tsx`:
- Line 24: Update the ProgressChartPage render of ProgressChartView to pass the
current workspace’s actual ID instead of the hardcoded 'test' value, using the
existing workspace context or page data source. Preserve the workspace-specific
mock task lookup performed by ProgressChartView.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 073eaf77-c697-42f8-a590-4f185576ab39
📒 Files selected for processing (1)
src/views/progress-chart/ui/ProgressChartPage.tsx
| return ( | ||
| <div className={`${plusJakartaSans.className} bg-brand-surface min-h-full`}> | ||
| <ProgressChartView workspaceId={workspaceId} /> | ||
| <ProgressChartView workspaceId={'test'} /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
실제 workspaceId를 전달해야 합니다.
ProgressChartView는 전달받은 ID로 워크스페이스별 mock 업무를 조회하는데, 현재 모든 비사이드 프로젝트가 'test'의 데이터를 렌더링합니다. 이로 인해 다른 워크스페이스에서 잘못된 진행률 차트가 표시됩니다.
- <ProgressChartView workspaceId={'test'} />
+ <ProgressChartView workspaceId={workspaceId} />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <ProgressChartView workspaceId={'test'} /> | |
| <ProgressChartView workspaceId={workspaceId} /> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/views/progress-chart/ui/ProgressChartPage.tsx` at line 24, Update the
ProgressChartPage render of ProgressChartView to pass the current workspace’s
actual ID instead of the hardcoded 'test' value, using the existing workspace
context or page data source. Preserve the workspace-specific mock task lookup
performed by ProgressChartView.
There was a problem hiding this comment.
현재 test 쪽 하드코딩으로 인해 team/store 워크스페이스에서도 test 목업 데이터가 표시될 수 있을 거 같습니다. 피드백처럼workspaceId={workspaceId}로 전달하도록 수정하면 좋을 거 같습니다. 아니면 혹시 test로 하신 이유가 있을까요..??
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -t f . src/features/manage-progress-chartLength of output: 5569
🏁 Script executed:
#!/bin/bash
rg -n "getMockTasksByWorkspaceId" -A 15 src/features/manage-progress-chart/model/progress-chart.tsLength of output: 418
🏁 Script executed:
#!/bin/bash
rg -n "getMockTasksByWorkspaceId" -A 20 -r "" src -g '*.ts' -g '*.tsx'
</br>
rg -n "getMockTasksByWorkspaceId" src -A 20
</br>
fd -t f -a mock src/features/manage-progress-chart
</br>
rg -n "workspaceId" src/features/manage-progress-chart -nLength of output: 8294
The comment/TODO in the file explains the intent, and I dug into manage-progress-chart to confirm the actual behavior:
src/features/manage-progress-chart/ui/ProgressChartView.tsxstill callsgetMockTasksByWorkspaceId(workspaceId)fromsrc/entities/task/model/mock-tasks-by-workspace.ts, which does a plain lookup:mockTasksByWorkspaceId[workspaceId]?.map(...) ?? [].- So
'test'was hardcoded because the mock dataset is keyed by that literal id, and this view (owned by another teammate per the code comment) hasn't been wired to Supabase yet — the TODO explicitly defers real integration to that follow-up work.
That said, the concern you and the original review raised is still valid: today, every non-side-project workspace (team/store) renders the same 'test' mock dataset instead of something scoped to its own id. Two options:
- Minimal safe fix now: switch to
workspaceId={workspaceId}. SincemockTasksByWorkspaceIdfalls back to[]for unknown ids, real workspaces would just show an empty/zeroed chart instead of someone else's'test'demo data — less confusing, and it's a no-risk one-line change. - Leave as-is: keep
'test'intentionally as a working demo placeholder until the real DB integration formanage-progress-chartlands, relying on the TODO comment to track it.
Given the risk of showing unrelated mock data across workspaces, I'd lean toward option 1 as a quick safety improvement even though it doesn't fully resolve the mock-data gap — but happy to leave it if you'd rather track this purely via the TODO for the other owner's follow-up.
🧬 Code Graph Analysis Results
Relevant snippets
src/views/progress-chart/ui/ProgressChartPage.tsx (provided in prompt, approx. line 1-27)
// side-project 진행률만 실 DB 연동 완료.
// 그 외 purpose는 manage-progress-chart 뷰(다른 담당자, mock 기반) — 실 API 연동 전이라
// workspaceId 'test' 하드코딩 상태. 실 연동은 해당 담당자 작업으로 남김.
// TODO(담당자): ProgressChartView 실 API 연동 + workspaceId={workspaceId} 전달
import { ProgressChartView } from '`@/features/manage-progress-chart`';
import { ProgressChartView as SideProjectProgressChartView } from '`@/views/side-project/progress-chart`';
import { plusJakartaSans } from '`@/shared/lib/fonts`';
import { getWorkspaceById } from '`@/entities/workspace/api/get-workspace-by-id`';
import { notFound } from 'next/navigation';
interface ProgressChartPageProps {
workspaceId: string;
}
export default async function ProgressChartPage({ workspaceId }: ProgressChartPageProps) {
const workspace = await getWorkspaceById(workspaceId);
if (!workspace) return notFound();
if (workspace.purpose === 'side-project') {
return <SideProjectProgressChartView workspaceId={workspaceId} />;
}
return (
<div className={`${plusJakartaSans.className} bg-brand-surface min-h-full`}>
<ProgressChartView workspaceId={'test'} />
</div>
);
}src/features/manage-progress-chart/ui/ProgressChartView.tsx (around lines 175-223)
export function ProgressChartView({ workspaceId }: ProgressChartViewProps) {
const tasks = getMockTasksByWorkspaceId(workspaceId);
const summary = createProgressChartSummary(tasks);
return (
<section className="w-full max-w-[1280px]">
<header className="mb-6">
<h1 className="text-brand-ink text-[28px] leading-[1.15] font-extrabold tracking-[-0.05em]">
진행률 차트
</h1>
</header>
<div className="grid grid-cols-1 gap-4 xl:grid-cols-12">
<div className="xl:col-span-4">
<SummaryNumberCard value={summary.totalTaskCount} label="전체 업무" />
</div>
<div className="xl:col-span-4">
<SummaryNumberCard
value={summary.doneTaskCount}
label="완료"
valueClassName="text-[`#00b73d`]"
/>
</div>
<div className="xl:col-span-4">
<SummaryNumberCard
value={summary.inProgressTaskCount}
label="진행 중"
valueClassName="text-[`#615bff`]"
/>
</div>
<div className="xl:col-span-6">
<OverallProgressCard
progress={summary.overallProgressRate}
doneCount={summary.doneTaskCount}
totalCount={summary.totalTaskCount}
/>
</div>
<div className="xl:col-span-6 xl:row-span-2">
<AssigneeBarChartCard items={summary.assigneeItems} />
</div>
<div className="xl:col-span-6">
<StatusDistributionCard items={summary.statusItems} />
</div>
</div>
</section>
);
}src/views/side-project/progress-chart/ui/ProgressChartView.tsx (around lines 28-64)
export function ProgressChartView({ workspaceId }: { workspaceId: string }) {
const sprintsQuery = useSprints(workspaceId);
// 진행 중(오늘이 기간 안) 스프린트 우선 → 없으면 최신. 로딩 중이면 undefined.
const sprint = sprintsQuery.data ? resolveCurrentSprint(sprintsQuery.data) : undefined;
const tasksQuery = useSprintTasks(sprint?.id);
if (sprintsQuery.isPending) return <CenteredMessage>불러오는 중…</CenteredMessage>;
if (sprintsQuery.isError) return <CenteredMessage>진행률을 불러오지 못했습니다.</CenteredMessage>;
// 스프린트가 하나도 없는 워크스페이스 — 빈 상태
if (!sprint) return <CenteredMessage>아직 생성된 스프린트가 없습니다.</CenteredMessage>;
if (tasksQuery.isPending) return <CenteredMessage>불러오는 중…</CenteredMessage>;
if (tasksQuery.isError) return <CenteredMessage>업무를 불러오지 못했습니다.</CenteredMessage>;
const velocity = selectVelocity(sprintsQuery.data);
const statusCounts = countByStatus(tasksQuery.data);
return (
<div className={`${jakarta.className} bg-brand-surface min-h-full`}>
<div className="flex flex-col gap-4">
<ProgressStatRow sprint={sprint} />
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
{/* 좌: 진행률 바(짧음) + 상태 도넛(김) 세로 스택 */}
<div className="flex flex-col gap-4">
<SprintProgressCard sprint={sprint} />
<StatusDonutChart counts={statusCounts} className="flex-1" />
</div>
{/* 우: 벨로시티 막대(좌측 컬럼 전체 높이) */}
<VelocityChart data={velocity} />
</div>
</div>
</div>
);
}src/entities/workspace/api/get-workspace-by-id.ts (around lines 7-29)
export const getWorkspaceById = cache(async (workspaceId: string): Promise<Workspace | null> => {
const supabase = await createSupabaseServerClient();
const { data, error } = await supabase
.from('workspaces')
.select('id, name, description, purpose')
.eq('id', workspaceId)
.maybeSingle();
if (error) {
throw new Error(`워크스페이스 조회에 실패했습니다: ${error.message}`);
}
if (!data) {
return null;
}
return {
id: data.id,
name: data.name,
description: data.description ?? undefined,
purpose: toUiPurpose(data.purpose),
};
});✏️ Learnings added
Learnt from: seongjinss555
Repo: TeampleRun/syncly PR: 44
File: src/views/progress-chart/ui/ProgressChartPage.tsx:24-24
Timestamp: 2026-07-13T06:31:12.288Z
Learning: In `src/views/progress-chart/ui/ProgressChartPage.tsx`, the non-side-project branch renders `@/features/manage-progress-chart`'s `ProgressChartView` with a hardcoded `workspaceId={'test'}` intentionally, because that view still uses mock data (`getMockTasksByWorkspaceId` from `src/entities/task/model/mock-tasks-by-workspace.ts`, keyed by literal workspace ids like `'test'`) and has not yet been migrated to Supabase. This is tracked via an in-file TODO comment as a follow-up for a different owner; using the real `workspaceId` would currently just return an empty task list since the mock dataset isn't keyed by real workspace ids.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| return ( | ||
| <div className={`${plusJakartaSans.className} bg-brand-surface min-h-full`}> | ||
| <ProgressChartView workspaceId={workspaceId} /> | ||
| <ProgressChartView workspaceId={'test'} /> |
There was a problem hiding this comment.
현재 test 쪽 하드코딩으로 인해 team/store 워크스페이스에서도 test 목업 데이터가 표시될 수 있을 거 같습니다. 피드백처럼workspaceId={workspaceId}로 전달하도록 수정하면 좋을 거 같습니다. 아니면 혹시 test로 하신 이유가 있을까요..??
| import { getSprintTasks, Task, TASK_STATUS, type TaskStatus } from '@/entities/side-project/task'; | ||
| import { | ||
| getMockSprintTasks, | ||
| type Task, |
There was a problem hiding this comment.
실 DB 연동 대상이라면 조회 실패 또는 빈 데이터 상황에서 목업으로 fallback하지 않는 방향이 맞을 거 같습니다. useSprintTasks와 같은 React Query 기반 조회 결과를 사용하고 로딩 중, 조회 실패, 정상 조회 시처럼 구분하여 표시하면 좋을 거 같습니다
지웅님 템플릿 진행률차트가 아직 db전환이 안되어있어서 workspaceId를 넣으면 팀플템플릿 진행률 차트에 아무것도 안뜨더라구요 그래서 팀프로젝트 진행률차트뷰는 기존에 지웅님 목 표시방식으로 하드코딩 해두었습니다! 추후 지웅님이 db전환하시면 'test'말고 원래 방식대로 워크스페이스 아이디 넣는 방식으로 전환해주시면 될거에요 아직 대시보드 위젯은 연결 안해놓은 상태입니다! 해당하는 위젯들은 일단 목데이터로 나두고 전환은 나중에 별도 이슈로 한번에 진행하려고 합니다!( 목 정리 + 위젯별 api엔티티 연결) |
seongjinss555
left a comment
There was a problem hiding this comment.
확인했습니다 승인해드릴게요~ 고생하셨습니다
Pull Request
작업 내용
sprint/task엔티티를 쓰기 때문에 함께 연동했습니다. 별도 데이터 소스 없이 스프린트 보드와 동일한useSprints(get_sprints RPC) /useSprintTasks훅을 재사용해, 스프린트 카드·벨로시티·상태 분포가 모두 실 DB에서 파생됩니다.useQuery), 쓰기는 서버액션 +useMutation(단일 컬럼 부분 수정은 클라 직접 update)으로 컨벤션(docs/conventions/supabase-convention.md)에 맞춰 배선했습니다.작업 결과
assignee_id저장.get_sprints읽기 RPC(additive)만 추가.변경 사항
Added
get_sprintsRPC (supabase/migrations/..._create_sprint_rpcs.sql) — 스프린트 목록 + 포인트 집계(total/completed) +days_left를 단일 쿼리로 반환 (N+1 없음, 테이블 스키마 불변).toTask/toSprint(읽기),toTaskInsert/toTaskUpdate,toSprintInsert/toSprintUpdate(쓰기). enum은GenericEnums로 파생(리터럴 중복 제거, §6).useSprints/useSprintTasks/useBacklogTasks(useQuery + 브라우저 클라이언트).createTask/updateTask/deleteTask서버액션(zod 재검증) +useCreateTask/useUpdateTask/useDeleteTask. DnD 상태 이동·백로그 편입은 클라 직접 update(updateTaskStatus/updateTaskSprint, §4).manage-sprints피처(SprintToolbar/SprintFormDialog/SprintDeleteDialog) +create/update/delete-sprint서버액션 + 뮤테이션. 날짜 검증(종료일 ≥ 시작일).update-task-sprint).ProgressChartView가useSprints/useSprintTasks+selectVelocity/countByStatus로 실 DB 파생.Changed
Task.assignee{ name, avatarLabel }→assigneeId. 표시명은 task 쿼리의profiles조인 대신 members(닉네임)에서 해석(BoardTask). 워크스페이스 닉네임 단일 출처.getWorkspaceMembersByWorkspaceId조회 후 prop 주입(mock 제거).features/sprint-board→features/manage-sprint-tasks(태스크 CRUD), 스프린트 CRUD는features/manage-sprints로 분리.['tasks']/['sprints']쿼리 무효화로 보드·차트·포인트 집계 동기화.Fixed
real_name(프로필), 선택 시workspace_nickname으로 표시가 갈리던 것을 닉네임으로 통일.새 스프린트노출.실행화면
테스트
npm run typecheck/lint/format:check통과 (변경 파일 기준)리뷰 체크리스트
feature/*->develop)Type/#issue-number/description형식을 따릅니다. (feat/#41/side-project-sprint-backend)console.log, 주석, 임시 코드를 제거했습니다.리뷰 요청사항
BoardTask) — task 쿼리에서 profiles 조인을 제거하고assigneeId만 두는 방향이 적절한지.get_sprintsRPC 집계(포인트/days_left)와 무효화 시점(쓰기 후['sprints']/['tasks'])이 진행률 차트까지 잘 반영되는지.관련 이슈
Closes #41
Summary by CodeRabbit